package com.fly.openapi.aspect;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;

import com.fly.core.entity.JsonResult;
import com.fly.core.exception.ValidateException;
import com.fly.core.utils.HttpServletUtils;
import com.fly.core.utils.JsonBeanUtils;
import com.fly.openapi.annotation.Item;
import com.fly.openapi.annotation.OpenApi;
import com.fly.openapi.service.AutoTokenService;
import com.fly.openapi.service.TimestampService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Order(0)
@Component
public class OpenApiAspect
{
    /**
     * 是否启用检查
     */
    @Value("${openapi.check.enable:true}")
    private boolean checkEnable;
    
    @Autowired
    AutoTokenService autoTokenService;
    
    @Autowired
    TimestampService timestampService;
    
    /**
     * 使用OpenApi注解标注的类
     * 
     * @throws IOException
     */
    @Before("@annotation(openApi)")
    public void doBefore(JoinPoint joinPoint, OpenApi openApi)
        throws IOException
    {
        if (checkEnable)
        {
            List<String> errors = new ArrayList<>();
            HttpServletRequest request = HttpServletUtils.getRequest();
            if (request == null)
            {
                log.error("OpenApi使用错误，当前请求非web请求");
                errors.add("OpenApi使用错误，当前请求非web请求");
            }
            else
            {
                String user = request.getHeader("user");
                List<Item> items = Arrays.asList(openApi.checkItems());
                if (items.contains(Item.ALL) || items.contains(Item.TOKEN))
                {
                    log.info("开始执行鉴权检查");
                    String token = request.getHeader("token");
                    if (!autoTokenService.isPass(user, token))
                    {
                        errors.add("对不起，您无权访问该接口，请检查user、token参数");
                    }
                }
                if (items.contains(Item.ALL) || items.contains(Item.NO_REPEAT))
                {
                    log.info("开始执行重复提交检查");
                    Long timestamp = NumberUtils.toLong(request.getHeader("timestamp"));
                    if (!timestampService.isFirstUse(user, timestamp))
                    {
                        errors.add("无效timestamp参数或重复请求");
                    }
                }
                
                if (items.contains(Item.ALL) || items.contains(Item.UN_MODIFY))
                {
                    log.info("需要防篡改");
                    String sign = request.getHeader("sign");
                    if (StringUtils.isBlank(sign))
                    {
                        errors.add("检查防篡改失败");
                    }
                }
            }
            
            // 汇总返回错误
            if (!CollectionUtils.isEmpty(errors))
            {
                // 方式一：统一异常处理
                if (RandomUtils.nextBoolean())
                {
                    throw new ValidateException(StringUtils.join(errors.toArray(), ","));
                }
                
                // 方式二：直接写入HttpServletResponse
                HttpServletResponse response = HttpServletUtils.getResponse();
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                try (ServletOutputStream out = response.getOutputStream())
                {
                    JsonResult<?> result = JsonResult.error(StringUtils.join(errors.toArray(), ","));
                    out.write(JsonBeanUtils.beanToJson(result, true).getBytes(StandardCharsets.UTF_8));
                }
            }
            
            // controller添加BindingResult，封装@Valid的验证结果，在切面中处理
            // 参考：https://blog.csdn.net/a932946893/article/details/122061233
            String errMsgs = Stream.of(joinPoint.getArgs())
                .filter(obj -> obj instanceof BindingResult) // 过滤
                .map(obj -> getErrorMsg((BindingResult)obj)) // 映射
                .sorted() // 排序
                .collect(Collectors.joining(","));
            if (StringUtils.isNotBlank(errMsgs))
            {
                throw new ValidateException(errMsgs);
            }
        }
    }
    
    private String getErrorMsg(BindingResult bindingResult)
    {
        return bindingResult.getFieldErrors().stream().map(e -> e.getDefaultMessage()).sorted().collect(Collectors.joining(","));
    }
}
